/**
 * 负责解析模板内容
 */
class Compile {
  constructor (el, vm) {
    // el: new vue时传递的选择器
    this.el= typeof el === 'string' ? document.querySelector(el) : el
    // vm: new的vue实例
    this.vm= vm
    // 编译模板
    if (this.el) {
      // 1. 把el中所有的子节点放入到内存中，fragment中
      let fragment = this.node2fragment(this.el)
      // 2. 在内存中编译fragment
      this.compile(fragment)
      // 3. 把fragment一次性添加到页面中 
      this.el.appendChild(fragment)
    }
  }

  /***********
   * 核心方法 *
   ***********/

  /**
   * 把node中所有子节点都添加到fragment中
   * @param {*} node 
   */
  node2fragment (node) {
    let fragment = new DocumentFragment()
    // 把el中所有的子节点挨个添加到文档碎片中
    let childNodes = node.childNodes
    this.toArray(childNodes).forEach(node => {
      // 把所有子节点都添加到fragment中
      fragment.appendChild(node)
    })
    return fragment
  }

  /**
   * 在内存中编译文档碎片
   * @param {*} fragment 
   */
  compile (fragment) {
    let childNodes = fragment.childNodes
    // 编译子节点
    this.toArray(childNodes).forEach(node => {
      // 元素节点
      if (this.isElementNode(node)) {
        this.compileElement(node)
      }
      // 文本节点
      if (this.isTextNode(node)) {
        this.compileText(node)
      } 
      // 如果当前节点还有子节点，需要递归的解析
      if (node.childNodes && node.childNodes.length > 0) {
        this.compile(node)
      }
    })
  }

  /**
   * 解析指令
   * @param {*} node 
   */
  compileElement(node) {
    // 1. 获取当前节点下的所有属性
    let attributes = node.attributes
    this.toArray(attributes).forEach(attr => {
      // 2. 解析vue的指令（所有以v-开头的属性）
      let attrName = attr.name
      if (this.isDirective(attrName)) {
        let type = attrName.slice(2)
        let expr = attr.value
        // 解析v-on指令
        if (this.isEventDirective(type)) {
          CompileUtil["eventHanlder"](node, this.vm, type, expr)
        }
        else {
          CompileUtil[type] && CompileUtil[type](node, this.vm, expr)
        }
      }
    })
  }

  /**
   * 解析插值表达式
   * @param {*} node 
   */
  compileText(node) {
    CompileUtil.mustache(node, this.vm)
  }

  /***********
   * 工具方法 *
   ***********/

  /**
   * 将node转换为数组
   * @param {*} likeArray 
   */
  toArray (likeArray) {
    return [].slice.call(likeArray)
  }

  /**
   * 判断是否是元素节点
   * @param {*} node 
   */
  isElementNode (node) {
    return node.nodeType === 1
  }

  /**
   * 判断是否是文本节点
   * @param {*} node 
   */
  isTextNode (node) {
    return node.nodeType === 3
  }

  /**
   * 判断是否是指令
   * @param {*} attrName 
   */
  isDirective (attrName) {
    return attrName.startsWith('v-')
  }

  /**
   * 判断是否是事件指令
   * @param {*} attrName 
   */
  isEventDirective (type) {
    return type.split(':')[0] === 'on'
  }

}

/**
 * 处理指令
 */
let CompileUtil = {
  mustache (node, vm) {
    let text = node.textContent
    // 匹配插值表达式
    let reg = /\{\{(.+)\}\}/
    if (reg.test(text)) {
      let expr = RegExp.$1
      node.textContent = text.replace(reg, this.getVMValue(vm, expr))
      new Watcher(vm, expr, newValue => {
        node.textContent = newValue
      })
    }
  },
  // 处理v-text指令
  text (node, vm, expr) {
    node.textContent = this.getVMValue(vm, expr)
    new Watcher(vm, expr, newValue => {
      node.textContent = newValue
    })
  },
  // 处理v-html指令
  html (node, vm, expr) {
    node.innerHTML = this.getVMValue(vm, expr)
    new Watcher(vm, expr, newValue => {
      node.innerHTML = newValue
    })
  },
  // 处理v-model指令
  model (node, vm, expr) {
    let seft = this
    node.value = this.getVMValue(vm, expr)
    // 实现双向数据绑定
    node.addEventListener('input', function () {
      seft.setVMValue(vm, expr, this.value)
    })
    new Watcher(vm, expr, newValue => {
      node.value = newValue
    })
  },
  // 处理v-on:指令
  eventHanlder (node, vm, type, expr) {
    // 给当前元素注册事件
    let eventType = type.split(":")[1]
    let fn = vm.$methods && vm.$methods[expr]
    if (eventType && fn) {
      // bind() 方法创建一个新的函数，在 bind() 被调用时，这个新函数的 this 被指定为 bind() 的第一个参数，而其余参数将作为新函数的参数，供调用时使用。
      node.addEventListener(eventType, fn.bind(vm))
    }
  },
  // 获取VM中的数据
  getVMValue (vm , expr) {
    let data = vm.$data
    expr.split(".").forEach(key => {
      data = data[key]
    })
    return data
  },
  // 设置VM中的数据
  setVMValue(vm , expr, value) {
    let data = vm.$data
    let arr = expr.split('.')
    arr.forEach((key, index) => {
      if (index < arr.length - 1) {
        data = data[key]
      }
      else {
        data[key] = value
      }
    })
  }
}